﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing.Text;
using System.Windows.Forms;
using System.Drawing;
using TurtleLib.ComputerCraft;
using System.Drawing.Drawing2D;

namespace TurtleLib.Simulation
{
  public class Screen : ChildPanel
  {
    protected Font _font;
    protected ComputerCraft.TermString[] _textBuffer;
    protected Size _textArea;

    public Screen(DragablePanel parent)
      : base(parent)
    {
      _textBuffer = new ComputerCraft.TermString[0];
      _textArea = new Size(0, 0);

      SetFirstFont("Consolas", "Courier", "Lucida");
    }

    private void SetFirstFont(params string[] fontNames)
    {
      if (fontNames == null || fontNames.Length == 0)
        return;

      var installedFonts = new InstalledFontCollection();

      foreach (var name in fontNames)
      {
        var fontFamily = installedFonts.Families.FirstOrDefault(fam => fam.Name.ToLower().Contains(name.ToLower()));
        if (fontFamily != null)
        {
          var exactName = fontFamily.Name;
          _font = new Font(exactName, DragablePanel.DEFAULT_FONT_SIZE, FontStyle.Regular);
          break;
        }
      }
    }

    public void SetTitle(string title)
    {
      _parent.Title = title;
    }

    protected void SubscribeToStatusUpdates(Computer comp)
    {
      var caccess = new Computer.ComputerAccess(comp);
      var oaccess = new TurtleLib.ComputerCraft.OperatingSystem.OperatingSystemAccess(caccess.OS);
      oaccess.StatusUpdateCallback = HandleStatusUpdate;
    }

    protected virtual void HandleStatusUpdate(string message)
    {
      _parent.StatusMessage = message ?? string.Empty;
    }

    public void SubscribeTo(Term host)
    {
      host.TextWritten += (term, textTable) =>
      {
        WriteText(textTable);
      };

      var size = host.getSize();
      SetOutputSize(size.Item1, size.Item2);
      _parent.Show();
    }

    public void SetOutputSize(int width, int height)
    {
      _textArea = new Size(width, height);

      var textBounds = GetTextBoxBounds();
      this.Size = textBounds.Size;
    }

    public override void Render(Graphics g)
    {
      // I know Rectangle is a struct, and will copy and not ref
      // but just to make it clear to the reader we are not changing the Bounds during render
      var outputBox = new Rectangle(Bounds.Location, Bounds.Size);
      outputBox.Height -= 2;
      var childArea = outputBox;
      childArea.Offset(2, 2);
      childArea.Width -= 4;
      childArea.Height -= 2;
      g.FillRectangle(new SolidBrush(Color.Black), outputBox);

      RenderText(g, outputBox);
      g.DrawRectangle(new Pen(Color.White, 2f), outputBox);
    }

    protected virtual void RenderText(Graphics g, Rectangle outputBox)
    {
      var allText = string.Join("\n", _textBuffer.Select(ts => string.Join("", ts.GetSegments().Select(seg => seg.Text))));

      if (string.IsNullOrEmpty(allText))
        return;

      Brush fontBrush = new SolidBrush(Color.White);

      g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
      g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

      StringFormat format = new StringFormat();
      GraphicsPath gpath = new GraphicsPath();

      var fontAlign = outputBox.Location;
      fontAlign.Offset(-2, 0);
      gpath.AddString(allText, _font.FontFamily, (int)_font.Style, _font.Size, fontAlign, format);

      g.FillPath(fontBrush, gpath);

      fontBrush.Dispose();
      gpath.Dispose();
    }

    protected Rectangle GetTextBoxBounds()
    {
      string full = GetSampleText();
      return GetTextBoxBounds(full);
    }

    protected virtual Rectangle GetTextBoxBounds(string text)
    {
      GraphicsPath gpath = new GraphicsPath();
      StringFormat format = StringFormat.GenericDefault;
      gpath.AddString(text, _font.FontFamily, (int)_font.Style, _font.Size, Point.Empty, format);

      gpath.Flatten();

      var recf = gpath.GetBounds();
      var rect = new Rectangle(Location, new Size((int)(recf.Left + recf.Width) + 10, (int)(recf.Top + recf.Height) + 10));
      return rect;
    }

    protected string GetSampleText()
    {
      string text = "y".PadRight(_textArea.Width, 'A');
      string full = string.Empty;
      string s = string.Empty;

      for (int i = 0; i < _textArea.Height; i++)
      {
        full += s + text;
        s = "\n";
      }

      return full;
    }

    private Size GetStringSize(int length, float size)
    {
      StringFormat format = StringFormat.GenericDefault;

      string single = "X";
      string dual = "XX\nXX";

      GraphicsPath gpath = new GraphicsPath();
      gpath.AddString(single, _font.FontFamily, (int)_font.Style, size, Point.Empty, format);
      gpath.Flatten();

      var singleRect = gpath.GetBounds();

      gpath = new GraphicsPath();
      gpath.AddString(dual, _font.FontFamily, (int)_font.Style, size, Point.Empty, format);
      gpath.Flatten();

      var dualRect = gpath.GetBounds();

      float width = dualRect.Width - singleRect.Width;
      width *= length;

      float height = dualRect.Height - singleRect.Height;

      return new SizeF(width, height).ToSize();
    }

    public void WriteText(ComputerCraft.TermString[] textTable)
    {
      _textBuffer = textTable;
      _parent.Invalidate();
    }

    public override Rectangle Resize(DragablePanel.ResizingArgs arg)
    {
      if (arg.Cancel)
        return Bounds;

      if (!SetFontSize(_font.Size + (float)(arg.Expanding ? 1 : -1)))
        arg.Cancel = true;

      return Bounds;
    }

    public bool SetFontSize(float size)
    {
      if (!ValidFontSize(size))
        return true; // doesn't fail, just ignore the resize

      _font = new Font(_font.Name, size, FontStyle.Regular);

      if (!_parent.Visible)
      {
        var textBounds = GetTextBoxBounds();
        this.Size = new Size(textBounds.Location.X + textBounds.Width + 5, textBounds.Location.Y + textBounds.Height + 5);
        return true;
      }

      Bounds = GetTextBoxBounds();
      return true;
    }

    protected virtual bool ValidFontSize(float size)
    {
      return size >= 2f && size <= 20f;
    }
  }
}
